package ffte.core

import spinal.core._
import spinal.lib._
import ffte.property.{getFFTIW,getFFTOW}
import ffte.algorithm.FFTGen.{combineFT,Method}
import ffte.types.{VecComplex}
import ffte.default.config

/**
 * FFT component interface trait
 *
 * Defines the common interface for all FFT implementations including timing
 * characteristics and data width specifications. This trait serves as the
 * foundation for all hardware FFT components in the FFTE system.
 *
 * Interface Requirements:
 * - All FFT implementations must specify data widths and timing
 * - Enables parameter comparison between different implementations
 * - Provides interface for system integration and timing analysis
 *
 * @param dW Input data width in bits
 * @param oW Output data width in bits
 * @param delay Component latency in clock cycles
 */
trait fft {
    /** Input data width in bits (precision of input complex samples) */
    def dW: Int

    /** Output data width in bits (precision of output complex samples) */
    def oW: Int

    /** Component delay in clock cycles (latency from input to output) */
    def delay: Int
}

/**
 * Combined FFT implementation trait
 *
 * Provides common functionality for multi-stage FFT implementations that combine
 * two smaller FFT operations. This trait handles data width management, scaling
 * distribution, and intermediate precision calculations for combined transforms.
 *
 * Scaling Strategy:
 * Fixed-point FFT computations require scaling to prevent overflow in intermediate
 * results. This trait intelligently distributes scaling bits between the two
 * stages to optimize precision while avoiding overflow.
 *
 * Mathematical Foundation:
 * For combined FFT with N = N1 * N2:
 * - Total scaling bits S are distributed between stages
 * - First stage gets scaling based on its contribution to total computation
 * - Second stage gets remaining scaling bits
 * - Intermediate width calculated to preserve precision
 *
 * @tparam T Implementation method type (Flat, Mixed, Streamed)
 */
trait combine[T <: Method] {
    /** Total scaling shift amount to prevent overflow */
    def S: Int

    /** FFT decomposition structure containing stage information */
    def p: combineFT[T]

    /** Input data width from global configuration */
    val dW = getFFTIW()

    /** Output data width from global configuration */
    val oW = getFFTOW()

    /** First (inner) stage FFT processor */
    val first = p.ib

    /** Second (outer) stage FFT processor */
    val second = p.ob

    /** Total transform size N = N1 * N2 */
    val N = p.N

    /**
     * Scaling bits allocated to first stage
     *
     * Distributes scaling based on the relative computational contribution
     * of each stage. First stage typically needs less scaling as it operates
     * on smaller transforms.
     *
     * Formula: round(S * log(N1) / log(N))
     * where N1 = first.N, N2 = second.N
     */
    val first_shift = Math.round(S * Math.log(first.N) / Math.log(second.N * first.N)).toInt

    /**
     * Scaling bits allocated to second stage
     *
     * Remaining scaling bits after first stage allocation.
     * Second stage typically needs more scaling as it combines results
     * from multiple first-stage transforms.
     */
    val second_shift = S - first_shift

    /**
     * Intermediate data width between stages
     *
     * Calculated to preserve precision while preventing overflow between
     * stages. Based on the relative computational complexity and data
     * widths of each stage.
     *
     * Formula: round((oW*log(N1) + dW*log(N2)) / log(N))
     * Ensures sufficient precision for accurate intermediate results
     */
    val interW = Math.round((oW * Math.log(first.N) + dW * Math.log(second.N)) / Math.log(second.N * first.N)).toInt
}

/**
 * Global width configuration trait
 *
 * Provides access to globally configured data widths for FFT components.
 * This trait enables components to use the system-wide data width settings
 * without needing explicit width parameters.
 *
 * Global Configuration:
 * - Input width (dW): Configured precision for input complex samples
 * - Output width (oW): Configured precision for output complex samples
 * - Retrieved from global FFTE property system
 *
 * Usage Pattern:
 * Components that extend this trait automatically get access to the
 * globally configured data widths, ensuring consistency across the system.
 */
trait widthed {
    /** Input data width from global configuration (fixed-point precision) */
    val dW = getFFTIW()

    /** Output data width from global configuration (fixed-point precision) */
    val oW = getFFTOW()
}

/**
 * Same width configuration trait
 *
 * Used for FFT components where input and output data widths are identical.
 * This is common in many FFT implementations where the same precision is
 * maintained throughout the computation.
 *
 * Configuration Pattern:
 * - Input width is specified as a parameter
 * - Output width automatically matches input width
 * - Ensures consistent precision throughout the component
 *
 * Use Cases:
 * - Reference FFT implementations
 * - Testing and validation components
 * - Simple FFT processors where precision scaling is not needed
 *
 * @param dW Data width in bits (used for both input and output)
 */
trait samewidthed {
    /** Input data width in bits (also used for output) */
    def dW: Int

    /** Output data width automatically matches input width */
    def oW = dW
}

/**
 * Streamed bus factory object
 *
 * Creates streamed data buses with fragment support for hardware FFT components.
 * This enables efficient pipelined data transfer between FFT stages with proper
 * flow control and data fragmentation.
 *
 * Streamed Bus Characteristics:
 * - Flow control: Ready/valid handshake for data transfer
 * - Fragmentation: Support for variable-length data bursts
 * - Type safety: Generic type support for different data types
 * - Pipeline compatibility: Designed for streaming hardware architectures
 *
 * @tparam T Data type to be transported over the bus
 * @return Stream with Fragment support for the specified data type
 */
object StreamedBus {
    /**
     * Create a streamed bus with fragment support
     *
     * @param itype Hardware type definition for the data
     * @tparam T Data type to be transported
     * @return Stream(Fragment[T]) with flow control
     */
    def apply[T <: Data](itype: HardType[T]) = Stream(Fragment(itype()))
}

/**
 * Streamed FFT I/O interface bundle
 *
 * Defines the input and output interfaces for streamed FFT implementations.
 * This bundle provides the standard interface for pipelined FFT components
 * that process data in a streaming fashion.
 *
 * Interface Characteristics:
 * - Input: G complex samples with input precision dW
 * - Output: G complex samples with output precision oW
 * - Flow Control: Ready/valid handshake on both ports
 * - Fragmentation: Support for burst data transfer
 * - Latency: Configurable pipeline depth based on implementation
 *
 * Timing Requirements:
 * - Input data must be valid when ready is asserted
 * - Output data becomes valid after pipeline latency cycles
 * - Both interfaces support backpressure through ready signals
 *
 * @param G Number of complex samples per transfer
 * @param dW Input data width in bits
 * @param oW Output data width in bits
 */
case class streamedIO(G: Int, dW: Int, oW: Int) extends Bundle {
    /**
     * Input data port
     *
     * Receives G complex samples with input precision.
     * Slave interface with flow control and fragmentation support.
     */
    val d = slave(StreamedBus(VecComplex(G, dW)))

    /**
     * Output data port
     *
     * Produces G complex samples with output precision.
     * Master interface with flow control and fragmentation support.
     */
    val q = master(StreamedBus(VecComplex(G, oW)))
}

/**
 * Streamed group configuration
 *
 * Configures the grouping of samples for streamed FFT implementations.
 * This allows multiple samples to be processed together, improving
 * hardware utilization and throughput.
 *
 * Group Configuration Benefits:
 * - Throughput: Multiple samples processed per cycle
 * - Efficiency: Better hardware resource utilization
     * - Latency: Reduced per-sample latency through parallelism
 * - Flexibility: Configurable based on system requirements
 *
 * @param groups Number of samples to group together
 */
case class StreamedGroupConfig(groups: Int) {
    /**
     * Execute code within this group configuration context
     *
     * Sets the StreamedGroupProperty for the duration of the code block,
     * ensuring consistent group configuration throughout the computation.
     *
     * @param body Code to execute with this group configuration
     * @tparam B Return type of the code block
     * @return Result of executing the code block
     */
    def on[B](body: => B) = {
        StreamedGroupProperty(this) on {
            body
        }
    }

    /**
     * Alternative syntax for executing code with group configuration
     *
     * @param body Code to execute with this group configuration
     * @tparam B Return type of the code block
     * @return Result of executing the code block
     */
    def apply[B](body: => B): B = on(body)
}

/**
 * Streamed group property scope
 *
 * Manages the current group configuration within a specific scope.
 * This enables different parts of the system to use different group
 * configurations while maintaining thread safety.
 *
 * Scope Management:
 * - Hierarchical property inheritance
 * - Thread-safe configuration access
 * - Default configuration fallback
 * - Dynamic configuration changes
 */
object StreamedGroupProperty extends ScopeProperty[StreamedGroupConfig] {
    /** Default group configuration from global settings */
    override def default: StreamedGroupConfig = config.DefaultStreamedGroupConfig
}

/**
 * Streamed group accessor object
 *
 * Provides convenient access to the current group configuration.
 * This is the primary way for FFT components to determine the
 * current grouping strategy.
 *
 * Usage Pattern:
 * Components call getStreamedGroup() to determine how many samples
 * should be processed together in the current context.
 *
 * @return Current group configuration value
 */
object getStreamedGroup {
    /**
     * Get the current stream group configuration
     *
     * @return Number of samples to group together
     */
    def apply(): Int = StreamedGroupProperty.get.groups
}